﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TracedReality.RayTracing;

using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// A finite version of the Plane.
    /// </summary>
    public class FinitePlane : Plane
    {
        /// <summary>
        /// Create a FinitePlane with the given point, direction vectors, and Material.
        /// The direction Vectors are not normalized as these also give the distance along
        /// that direction.
        /// </summary>
        /// <param name="p">Point.</param>
        /// <param name="a">First direction.</param>
        /// <param name="b">Second direction.</param>
        /// <param name="m">Material.</param>
        public FinitePlane(Vector p, Vector a, Vector b, ref Material m)
        {
            point = p;
            d1 = a;
            d2 = b;
            material = m;

            Vector z = point + d1;
            Vector y = point + d2;
            Vector x = point + d1 + d2;
            Vector min = Vector.Min(z, Vector.Min(y, Vector.Min(x, point))) - Settings.EPSILON;
            Vector max = Vector.Max(z, Vector.Max(y, Vector.Max(x, point))) + Settings.EPSILON;
            bound = new BoundingBox(min, max);
        }

        public override bool Similar(Shape s)
        {
            if (!(s is FinitePlane)) { return false; }
            FinitePlane f = (FinitePlane)s;

            return d1.Equals(f.d1) &&
                d2.Equals(f.d2) &&
                material.Equals(f.material);
        }

        /// <summary>
        /// Test if the given Ray intersects this FinitePlane.
        /// </summary>
        /// <param name="ray">Ray to test for intersection.</param>
        /// <param name="hit">Hit to update with intersection info.</param>
        /// <returns>1 if the Ray intersects this FinitePlane from the front,
        /// -1 if the Ray intersects this FinitePlane from the back,
        /// 0 if no intersection occurs.</returns>
        public override int Intersect(Ray ray, Hit hit)
        {
            Hit h = new Hit();
            int inter = base.Intersect(ray, h);
            if (inter == 0) { return 0; }

            // Such a hack but it works.
            if (h.t > Settings.EPSILON && h.t < hit.t && bound.contains(ray.pointAtParameter(h.t)))
            {
                hit.t = h.t;
                hit.normal = h.normal;
                hit.material = h.material;
                
                return inter;
            }

            return 0;
        }

        public static new FinitePlane Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            Material material = Material.Parse(map.Find("material"));
            MappingEntry found = map.Find("corner");
            if (found == null) { throw new ArgumentException("No corner entry"); }
            Vector corner = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("dir1");
            if (found == null) { throw new ArgumentException("No dir1 entry"); }
            Vector dir1 = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("dir2");
            if (found == null) { throw new ArgumentException("No dir2 entry"); }
            Vector dir2 = Vector.FromSequence((Sequence)found.Value);

            return new FinitePlane(corner, dir1, dir2, ref material);
        }

        /// <summary>
        /// Get a string representation of this FinitePlane.
        /// </summary>
        /// <returns>A string representing this FinitePlane.</returns>
        public override string ToString()
        {
            return "FinitePlane: { corner: " + point + ", dir1: " + d1 + ", dir2: " + d2 + ", material: " + material + " }";
        }
    }
}
